package com.xgy.vue_role_system.config.security.filter;

import com.xgy.vue_role_system.config.redis.RedisService;
import com.xgy.vue_role_system.config.security.exception.CustomerAuthenticationException;
import com.xgy.vue_role_system.config.security.handler.LoginFailureHandler;
import com.xgy.vue_role_system.config.security.service.CustomerUserDetailsService;
import com.xgy.vue_role_system.utils.JwtUtils;

import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
 * 在SpringSecurity过滤链前添加 jwt 验证
 */
@Data
@Component
public class CheckTokenFilter extends OncePerRequestFilter {
    //@ConfigurationProperties用于批量绑定配置文件中的配置；@Value只能一个一个的指定需要绑定的位置，绑定粒度更小
    @Value("${request.login.url}")
    private String loginUrl; //自定义登录请求地址

    @Autowired
    private LoginFailureHandler loginFailureHandler;

    @Autowired
    private RedisService redisService;

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private CustomerUserDetailsService customerUserDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        try {
            //获取当前请求的url地址
            String url = request.getRequestURI();
            //判断当前请求是否是登录请求，如果不是登录请求，则进行token验证
            if(!url.equals(loginUrl)){
                //进行token验证，如果验证失败会抛出自定义异常CustomerAuthenticationException
                this.validateToken(request);
            }
        } catch (CustomerAuthenticationException e) {
            //抛出的所有异常，交由loginFailureHandler去进行异常处理，统一返回前端结果
            loginFailureHandler.onAuthenticationFailure(request,response, e);
        }
        //登录请求不需要携带token，直接放行
        doFilter(request,response,filterChain);
    }



    /**
     * 验证token信息
     * @param request
     */
    private void validateToken(HttpServletRequest request) throws CustomerAuthenticationException {
        //获取前端提交过来的token
        //先从headers头部获取token信息
        String token = request.getHeader("token");
        //如果请求头中没有token，则从请求参数中获取token
        if(ObjectUtils.isEmpty(token)){
            token = request.getParameter("token"); //从参数中获取
        }
        //如果请求参数中也没有携带token信息，则抛出异常
        if(ObjectUtils.isEmpty(token)){
            throw new CustomerAuthenticationException("token不存在！");
        }

        //判断Redis中是否存在该token
        String tokenKey = "token_" + token;
        String redisToken = redisService.get(tokenKey);
        //如果redisToken为空，则表示token已经失效
        if(ObjectUtils.isEmpty(redisToken)){
            throw new CustomerAuthenticationException("token已失效！");
        }
        //如果如果redisToken不为空，判断 token 与 redisToken 是否一致
        if(!token.equals(redisToken)){
            throw new CustomerAuthenticationException("token验证失败！");
        }
        //从token中解析出用户名
        String username = jwtUtils.getUsernameFromToken(token);
        //判断用户名是否为空
        if(ObjectUtils.isEmpty(username)){
            throw new CustomerAuthenticationException("token解析失败！");
        }
        //获取用户信息
        UserDetails userDetails = customerUserDetailsService.loadUserByUsername(username);
        //判断用户信息是否为空
        if(ObjectUtils.isEmpty(userDetails)){
            throw new CustomerAuthenticationException("token验证失败！");
        }
        //创建用户身份认证对象
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        //设置请求信息
        //authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        //将认证的信息交给 SpringSecurity 上下文
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }
}
